package com.nuc.bigdata.aspect;

import com.nuc.bigdata.annotation.ResponseResult;
import com.nuc.bigdata.exception.BusinessException;
import com.nuc.bigdata.model.Result;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * 对ResponseResult注解进行aop增强，定义切面
 * @author JackLuo
 * @date 2019/10/31
 */
@Aspect
@Component
public class ResultAdvice {

    /**
     * 定义切点为ResponseResult注解
     */
    @Pointcut("@annotation(com.nuc.bigdata.annotation.ResponseResult)")
    public void resultPoint() {
    }

    /**
     * 对有ResponseResult注解的进行包裹处理
     * @param joinPoint  加入点
     * @return 被注解的方法返回的对象
     */
    @Around("resultPoint()")
    public Object around(ProceedingJoinPoint joinPoint) {
        Method currentMethod = getCurrentMethod(joinPoint);
        // 断言当前被注解的方法必不为空(注解使用的方法必不为空)
        assert currentMethod != null;
        // 获取注解的值
        ResponseResult annotation = currentMethod.getAnnotation(ResponseResult.class);
        // ResponseResult注解的值默认为真(是布尔类型)
        if (annotation.value()) {
            try {
                // 接口运行(返回的Object对象就是接口运行的结果)
                Object proceed = joinPoint.proceed();
                // 将接口结果进行包装，这里只进行了成功的包装。
                // 如果业务中出现枚举类中定义的错误，应该怎么写呢？
                // 如果业务出现异常(传的参数不对或少了)，就抛出异常，在这里进行捕获，然后进行Result.failure(throwable.getXXX())
                // 所以业务异常中需要使用枚举类
                return Result.success(proceed);
            } catch (BusinessException e) {
                return Result.failure(e.getCode(),e.getMsg() ,e.getError());
            } catch (Throwable throwable) {
                throwable.printStackTrace();
                return null;
            }
        } else {
            try {
                return joinPoint.proceed();
            } catch (Throwable throwable) {
                throwable.printStackTrace();
                return null;
            }
        }
    }

    @After("resultPoint()")
    public void after() {

    }

    @AfterReturning(pointcut = "resultPoint()", returning = "result")
    public void afterReturn(Object result) {

    }

    /**
     * 获取当前注解所注解的方法；joinPoint就是连接点，指的就是注解的方法体
     * 1. 通过连接点获取MethodSignature
     * 2. 通过连接点获取目标对象，该对象就是所注解的类的对象
     * 3. 通过该目标类对象来反射该类，再获取其中被注解所注解的方法
     *
     * @param joinPoint 连接点
     * @return ResponseResult注解的方法
     */
    private Method getCurrentMethod(ProceedingJoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Object target = joinPoint.getTarget();
        try {
            return target.getClass().getMethod(signature.getName(), signature.getParameterTypes());
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
            return null;
        }
    }
}
